package com.wm.blog_web.service;


import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.gson.JsonObject;
import com.wm.blog_common.annotation.BlogSearch;
import com.wm.blog_common.base.BeanUtils;
import com.wm.blog_common.bean.PageHelper;
import com.wm.blog_common.bean.QueryPage;
import com.wm.blog_common.constatnt.CommonConstant;
import com.wm.blog_common.constatnt.ErrorConstant;
import com.wm.blog_common.domain.TBlogDO;
import com.wm.blog_common.domain.TFileDO;
import com.wm.blog_common.entity.*;
import com.wm.blog_common.enums.*;
import com.wm.blog_common.exception.BusinessException;
import com.wm.blog_common.resp.BlogEsResp;
import com.wm.blog_common.result.Result;
import com.wm.blog_common.util.CommonUtil;
import com.wm.blog_common.util.DateUtil;
import com.wm.blog_common.util.StringUtil;
import com.wm.blog_common.util.excel.FieldReflectionUtil;
import com.wm.blog_config.Document.BlogDocument;
import com.wm.blog_web.controller.resp.BlogDetailResp;
import com.wm.blog_web.controller.resp.BlogMonthCountResp;
import com.wm.blog_web.controller.resp.BlogResp;
import com.wm.blog_web.dao.*;
import com.wm.blog_web.feign.PictureFeignClient;
import com.wm.blog_web.feign.SearchFeignClient;
import com.wm.blog_web.query.TBlogQuery;
import com.wm.blog_web.req.BlogSaveReq;
import com.wm.blog_web.task.BlogSaveTask;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import javax.websocket.server.PathParam;
import java.lang.reflect.Field;
import java.time.LocalDate;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

/**
 * <p>
 * 博客表 服务实现类
 * </p>
 *
 * @author wm_yu
 * @since 2020-03-24
 */
@Service
@Slf4j
public class TBlogService {
    @Autowired
    private TBlogDAO tBlogDAO;

    @Autowired
    private TBlogSortDAO tBlogSortDAO;

    @Autowired
    private PictureFeignClient pictureFeignClient;

    @Autowired
    private TCommentDAO tCommentDAO;

    @Autowired
    private TTagDAO tTagDAO;

    @Autowired
    private TUserDAO tUserDAO;

    @Autowired
    private TAdminDAO tAdminDAO;

    @Autowired
    private SearchFeignClient searchFeignClient;

    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    /**
     * 根据ID查询
     *
     * @param id
     * @return
     */
    public Result<TBlogDO> selectById(Long id) {
        return Result.success(BeanUtils.copyProperties(tBlogDAO.getById(id), TBlogDO.class));
    }

    /**
     * 根据ID删除
     *
     * @param id
     * @return
     */
    public Result deleteById(Long id) {
        return tBlogDAO.removeById(id) ? Result.success() : Result.failure();
    }

    /**
     * 添加
     *
     * @param tBlog
     * @return
     */
    public Result insert(TBlog tBlog) {
        return tBlogDAO.save(tBlog) ? Result.success() : Result.failure();
    }

    /**
     * 修改
     *
     * @param tBlog
     * @return
     */
    public Result update(TBlog tBlog) {
        return tBlogDAO.updateById(tBlog) ? Result.success() : Result.failure();
    }

    /**
     * 统计总数
     *
     * @param query
     * @return
     */
    public Result<Integer> count(TBlogQuery query) {
        return Result.success(tBlogDAO.count(this.combineQuery(query)));
    }

    /**
     * 列表查询
     *
     * @param query
     * @return
     */
    public Result<List<TBlogDO>> list(TBlogQuery query) {
        return Result.success(BeanUtils.copyListProperties(tBlogDAO.list(this.combineQuery(query)), TBlogDO.class));
    }


    /**
     * 分页查询
     *
     * @param page
     * @return
     */
    public Result<Page<TBlogDO>> page(QueryPage page, TBlogQuery query) {
        if(query.getIsMyBlog() && -1 == query.getUserId()){
            return Result.failure(ErrorConstant.USER_NO_LOGIN);
        }
        //只展示已经发布的文章
        query.setIsPublish(PublishFlag.PUBLISH.getValue());
        QueryWrapper<TBlog> wrapper = this.combineQuery(query);
        Page<TBlogDO> newPage = PageHelper.newPage(tBlogDAO.page(page, wrapper), TBlogDO.class);
        if (!ObjectUtils.isEmpty(newPage) && !CollectionUtils.isEmpty(newPage.getRecords())) {
            //图片id
            List<Long> picIdList = newPage.getRecords()
                    .stream()
                    .distinct()
                    .map(TBlogDO::getFileId)
                    .collect(Collectors.toList());
            if (!CollectionUtils.isEmpty(picIdList)) {
                //图片map  k:id v:url
                Map<Long, String> picMap = Optional
                        .ofNullable(pictureFeignClient.getByIdList(picIdList).getData())
                        .orElse(Collections.emptyList())
                        .stream()
                        .collect(Collectors.toMap(TFile::getId, TFile::getPicUrl));
                List<TBlogDO> resultList = newPage.getRecords()
                        .stream()
                        .filter(org.apache.commons.lang3.ObjectUtils::isNotEmpty)
                        .peek(item -> {
                            item.setPicUrl(picMap.getOrDefault(item.getFileId(), null));
                        })
                        .collect(Collectors.toList());
                //处理标签list
                CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                    //标签idlist
                    List<Long> idList = new ArrayList<>();
                    resultList.forEach(item -> {
                        if (StringUtil.isNotEmpty(item.getTagId())) {
                            List<Long> longList = Arrays
                                    .stream(item.getTagId().split(","))
                                    .map(Long::parseLong)
                                    .distinct()
                                    .collect(Collectors.toList());
                            if (!CollectionUtils.isEmpty(longList)) {
                                longList.stream().filter(x -> !idList.contains(x)).forEach(idList::add);
                            }
                        }
                    });
                    //查询标签map, key:id value:name
                    Map<Long, TTag> map = Optional
                            .ofNullable(tTagDAO.listByIds(idList))
                            .orElse(Collections.emptyList())
                            .stream()
                            .collect(Collectors.toMap(TTag::getId, v -> v));

                    resultList.stream().filter(item -> StringUtil.isNotEmpty(item.getTagId())).forEach(x -> {
                        List<TTag> list = new ArrayList<>();
                        List<Long> longList = Arrays
                                .stream(x.getTagId().split(","))
                                .map(Long::parseLong)
                                .distinct()
                                .collect(Collectors.toList());
                        if (!CollectionUtils.isEmpty(longList)) {
                            longList.forEach(y -> {
                                list.add(map.getOrDefault(y, null));
                            });
                        }
                        x.setTagList(list);
                    });
                }, threadPoolTaskExecutor);
                try {
                    future.get();
                } catch (InterruptedException | ExecutionException e) {
                    log.error("获取线程数据异常");
                }
                newPage.setRecords(resultList);
            }
        }
        return Result.success(newPage);
    }


    /**
     * 组装查询条件
     *
     * @param query
     * @return
     */
    private QueryWrapper<TBlog> combineQuery(TBlogQuery query) {
        QueryWrapper<TBlog> wrapper = new QueryWrapper<>();
        Optional.ofNullable(query).ifPresent(x -> {
            Optional.ofNullable(x.getUserId()).filter(y -> -1 != y).ifPresent(y -> {
                wrapper.eq("user_id", y);
            });
            Optional.ofNullable(query.getIsPublish()).ifPresent(y -> {
                wrapper.eq("is_publish", y);
            });
            if(query.getIsMyBlog()){
                wrapper.eq("user_id",query.getUserId());
            }
            Optional.ofNullable(query.getStatus()).ifPresent(y -> {
                wrapper.eq("status",y);
            });
            Optional.ofNullable(query.getTagId()).ifPresent(y -> {
                wrapper.apply(String.format("FIND_IN_SET (%s,tag_id)",y));
            });
            Optional.ofNullable(query.getSortId()).ifPresent(y -> {
                wrapper.eq("blog_sort_id",y);
            });
            Optional.ofNullable(query.getDateTime()).filter(StringUtil::isNotEmpty).ifPresent(y -> {
                //组装时间,查询当前时间一个月内的数据,
                String sql = "DATE_FORMAT(create_time, '%Y-%m') = DATE_FORMAT(" + "'"+y+"'" +  ",'%Y-%m')";
                wrapper.apply(sql);
            });
            Optional.ofNullable(query.getLast()).filter(StringUtils::isNotEmpty).ifPresent(wrapper::last);
        });
        return wrapper;
    }

    public Result hotBlog(Long userId) {
        TBlogQuery query = new TBlogQuery();
        //已发布的文章
        query.setIsPublish(PublishFlag.PUBLISH.getValue());
        query.setLast("limit 5");
        if (!ObjectUtils.isEmpty(userId)) {
            //用户未登录,查询所有最热的5篇文章
            query.setUserId(userId);
        }
        QueryWrapper<TBlog> wrapper = this.combineQuery(query);
        List<TBlog> list = Optional
                .ofNullable(tBlogDAO.list(wrapper))
                .orElse(Collections.emptyList());
        //查询文章的评论数/点赞数
        List<Long> blogIdList = list
                .stream()
                .map(TBlog::getId)
                .collect(Collectors.toList());
        List<BlogResp> respList = Optional.ofNullable(BeanUtils.copyListProperties(list, BlogResp.class)).orElse(Collections.emptyList());
        if (!CollectionUtils.isEmpty(blogIdList)) {
            QueryWrapper<TComment> commentWrapper = new QueryWrapper<>();
            commentWrapper.in("blog_id", blogIdList);
            //按照博客id分组,一个博客在评论表中体现是多个数据
            List<TComment> commentList = Optional
                    .ofNullable(tCommentDAO.list(commentWrapper))
                    .orElse(Collections.emptyList());
            //构建点赞和评论map  k--博客id,value--点赞/评论总数
            Map<Long, Integer> likeMap = new HashMap<>();
            Map<Long, Integer> commentMap = new HashMap<>();
            this.dealLikeCommentMap(likeMap, commentMap, commentList);
            List<BlogResp> resultList = respList
                    .stream()
                    .peek(x -> {
                        x.setCommentCount(commentMap.getOrDefault(x.getId(), 0));
                        x.setLikeCount(likeMap.getOrDefault(x.getId(), 0));
                    })
                    .collect(Collectors.toList());
            return Result.success(resultList);
        }
        return Result.success(list);
    }


    /**
     * //构建点赞和评论map  k--博客id,value--点赞/评论总数
     */
    private void dealLikeCommentMap(Map<Long, Integer> likeMap, Map<Long, Integer> commentMap, List<TComment> list) {
        if (CollectionUtils.isEmpty(list)) {
            return;
        }
        Map<Long, List<TComment>> map = list
                .stream()
                .collect(Collectors.groupingBy(TComment::getId));
        for (Map.Entry<Long, List<TComment>> entry : map.entrySet()) {
            Long id = entry.getKey();
            List<TComment> commentList = entry.getValue();
            Long commentCount = commentList
                    .stream()
                    .filter(x -> CommentFlag.COMMENT.getValue().equals(x.getType()))
                    .count();
            Long likeCount = commentList
                    .stream()
                    .filter(x -> CommentFlag.LIKE.getValue().equals(x.getType()))
                    .count();
            likeMap.put(id, likeCount.intValue());
            commentMap.put(id, commentCount.intValue());
        }
    }


    /**
     * 博客详情查看
     *
     * @param id
     * @return
     */
    public Result blogDetail(@PathParam("id") Long id) {
        if (ObjectUtils.isEmpty(id)) {
            return Result.failure(ErrorConstant.BLOG_ID_EMPTY);
        }
        BlogDetailResp detailResp = new BlogDetailResp();
        TBlog blog = Optional.ofNullable(tBlogDAO.getById(id)).orElse(new TBlog());
        if (!ObjectUtils.isEmpty(blog.getId())) {
            //博客基本信息
            detailResp.setBlogInfo(BeanUtils.copyProperties(blog, TBlogDO.class));
            //图片信息
            TFileDO fileDO = Optional.ofNullable(pictureFeignClient.get(blog.getFileId()).getData()).orElse(new TFileDO());
            detailResp.getBlogInfo().setPicUrl(fileDO.getPicUrl());
            detailResp.getBlogInfo().setCopyright("本文为流年博客原创文章，转载无需和我联系，但请注明来自流年博客 ");
            //博客分类信息
            TBlogSort blogSort = Optional.ofNullable(tBlogSortDAO.getById(blog.getBlogSortId())).orElse(new TBlogSort());
            detailResp.setBlogSort(blogSort);
            //标签信息
            TTag tag = Optional.ofNullable(tTagDAO.getById(blog.getTagId())).orElse(new TTag());
            detailResp.setTagList(new ArrayList<TTag>() {{
                add(tag);
            }});
        }
        return Result.success(detailResp);
    }

    /**
     * 保存博客
     *
     * @param req
     * @return
     */
    public Result save(BlogSaveReq req) {
        Optional.ofNullable(req.getUserId()).orElseThrow(() -> new BusinessException(ErrorConstant.USER_PUBLISH_LOGIN_ERROR));
        if(0 == req.getUserId() || -1 == req.getUserId()){
            return Result.failure(ErrorConstant.USER_PUBLISH_LOGIN_ERROR);
        }
        if(!MarkdownEnum.IS_MARKDOWN.getValue().equals(req.getIsMarkdown()) && !MarkdownEnum.NOT_MARKDOWN.getValue().equals(req.getIsMarkdown())){
            return Result.failure(ErrorConstant.BLOG_FLAG_MISS);
        }
        TBlog blog = new TBlog();
        BeanUtils.copyProperties(req, blog);
        //查询用户
        TUser user = Optional.ofNullable(tUserDAO.getById(req.getUserId())).orElse(new TUser());
        blog.setAuthor(user.getUserName());
        blog.setUserId(user.getId());
        //查询用户所属的管理员
        if (!ObjectUtils.isEmpty(user.getId())) {
            Long adminId = Optional.ofNullable(tAdminDAO.getById(user.getAdminId())).orElse(new TAdmin()).getId();
            blog.setAdminId(adminId);
        }
        if (StringUtil.isEmpty(req.getSummary())) {
            //未传入简介,自动生成最大100长度的简介
            blog.setSummary(CommonUtil.createSummary(req.getContent()));
        }
        //将标签数组转为逗号分割的字符串
        String tagStr = String.join(",", req.getTagIdList());
        blog.setTagId(tagStr);
        Date now = new Date();
        blog.setUpdateTime(now);
        blog.setCreateTime(now);
        blog.setStatus(DeleteFlag.ENABLE.getValue());
        blog.setArticlesPart(CommonConstant.BLOG_DEFAULT_ARTICLES_PART);
        boolean save = tBlogDAO.save(blog);
        //博客发布成功,往es添加数据,进行分词
        if(save){
            BlogDocument document = BeanUtils.copyProperties(blog, BlogDocument.class);
            threadPoolTaskExecutor.execute(new BlogSaveTask(document,searchFeignClient,EsOperationEnum.SAVE.getValue()));
        }
        return save ? Result.success():Result.failure();
    }

    /**
     * 根据关键词查询分页,从es中匹配数据,并高亮展示
     * @param keyword
     * @param current
     * @param size
     * @return
     */
    public Result esPage(Integer current,Integer size,String keyword) {
        if(StringUtil.isEmpty(keyword)){
            return Result.success();
        }
        current = Optional.ofNullable(current).orElse(1);
        size = Optional.ofNullable(size).orElse(1);
        //查询es

        Field[] fields = FieldReflectionUtil.getAllFields(BlogEsResp.class);
        List<String> list = Arrays
                .stream(fields)
                .filter(x -> x.isAnnotationPresent(BlogSearch.class))
                .map(Field::getName)
                .collect(Collectors.toList());
        String fieldString = String.join(",", list);
        Result<Page<Map<String, Object>>> result = searchFeignClient.queryHitByPage(current, size, keyword, CommonConstant.BLOG_ES_INDEX_NAME,fieldString);
        List<BlogEsResp> resultList = new ArrayList<>();
        //处理数据,将高亮数据和原始数据合并为一个对象
        if(result.isSuccess() && !ObjectUtils.isEmpty(result.getData()) && !CollectionUtils.isEmpty(result.getData().getRecords())){
            Page<Map<String, Object>> page = result.getData();
            List<Map<String, Object>> mapList = page.getRecords();
            Map<Object, Object> defaultMap = new HashMap<>();
            mapList.forEach(map -> {
                Map<String,Object> sourceMap = (Map<String, Object>) map.getOrDefault(CommonConstant.BLOG_ES_DEFAULT_SOURCE,defaultMap);
                Map<String,Object> highlightMap = (Map<String, Object>) map.getOrDefault(CommonConstant.BLOG_ES_DEFAULT_HIGHLIGHT, defaultMap);
                BlogEsResp resp = BeanUtils.map2Object(sourceMap, BlogEsResp.class);
                CommonUtil.dealBlogEsResp(resp,highlightMap);
                resultList.add(resp);
            });
            Page<BlogEsResp> newPage = PageHelper.newPage(page, resultList);
           return Result.success(newPage);
        }
        return result;
    }

    /**
     * 修改博客
     * @param req
     * @return
     */
    public Result editBlog(BlogSaveReq req) {
        Optional.ofNullable(req.getId()).orElseThrow(() -> new BusinessException(ErrorConstant.BLOG_ID_EMPTY));
        //查询博客作者
        TBlog blog = Optional.ofNullable(tBlogDAO.getById(req.getId())).orElse(new TBlog());
        if(ObjectUtils.isEmpty(blog.getId()) || !req.getUserId().equals(blog.getUserId())){
            return Result.failure(ErrorConstant.ACCOUNT_NOT_PERSSION);
        }
        TBlog tBlog = BeanUtils.copyProperties(req, blog);
        tBlog.setUpdateTime(DateUtil.getCurrentDate());
        //重新构建简介
        String summary = CommonUtil.createSummary(tBlog.getContent());
        tBlog.setSummary(summary);
        boolean update = tBlogDAO.updateById(tBlog);
        if(update){
            BlogDocument document = BeanUtils.copyProperties(tBlog,BlogDocument.class);
            //更新es中的文档数据
            threadPoolTaskExecutor.execute(new BlogSaveTask(document,searchFeignClient,EsOperationEnum.EDITOR.getValue()));
        }
        return update ? Result.success():Result.failure();
    }

    /**
     * 删除博客
     * @param req
     * @return
     */
    public Result deleteBlog(BlogSaveReq req) {
        Optional.ofNullable(req.getId()).orElseThrow(() -> new BusinessException(ErrorConstant.BLOG_ID_EMPTY));
        if(-1 == req.getUserId()){
            return Result.failure(ErrorConstant.USER_NO_LOGIN);
        }
        //查询当前博客
        TBlog blog = Optional.ofNullable(tBlogDAO.getById(req.getId())).orElse(new TBlog());
        if(ObjectUtils.isEmpty(blog.getId()) || !req.getUserId().equals(blog.getUserId())){
            return Result.failure(ErrorConstant.ACCOUNT_NOT_PERSSION);
        }
        boolean remove = tBlogDAO.removeById(req.getId());
        if(remove){
            //删除es文档数据
            BlogDocument document = new BlogDocument();
            document.setId(blog.getId());
            threadPoolTaskExecutor.execute(new BlogSaveTask(document,searchFeignClient, EsOperationEnum.DELETE.getValue()));
        }
       return remove ? Result.success():Result.failure();
    }

    /**
     * 博客数量通过月份统计
     * @return
     */
    public Result countByMonth() {
        TBlogQuery query = new TBlogQuery();
        query.setStatus(DeleteFlag.ENABLE.getValue());
        query.setIsPublish(PublishFlag.PUBLISH.getValue());
        List<TBlogDO> doList = Optional.ofNullable(tBlogDAO.countByMonth(query)).orElse(Collections.emptyList());
        List<BlogMonthCountResp> respList = BeanUtils.copyListProperties(doList, BlogMonthCountResp.class);
        if(!CollectionUtils.isEmpty(respList)){
            List<BlogMonthCountResp> resultList = respList.stream().peek(item -> {
                //处理时间
                item.setDateTime(item.getDateTime() + "-01");
                String dateStr = CommonUtil.dealBlogMonthTime(item.getDateTime());
                item.setDateStr(dateStr);
            }).collect(Collectors.toList());
            return Result.success(resultList);
        }
        return Result.success(Collections.emptyList());
    }
}








